/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package smtp;

import data.DataHandler;
import data.DataQueue;
import model.MailEnvelope;
import model.MailMIMEHeader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.StringTokenizer;

/**
 * @author lailiaidi
 * This class is to connect to SMTP Server, parsing the SMTP Server's respond
 */
public class SMTPClient extends Thread {

    //Declaring variables
    private String obStrServerAddress;
    private String obStrServerName;
    private Socket obSocket;
    private PrintWriter obOutput;
    private BufferedReader obInput;
    private MailEnvelope obMailEnvelope;
    private DataQueue obDataQueue;
    private boolean isConnected, isNeedConfirmation, isFutureDeliveryMail;
    //Declaring Constants
    final int obIntServerPort = 25;
    final String CRLF = "\r\n";

    public SMTPClient(String obStrServerAddress, String obStrServerName, MailEnvelope obMailEnvelope, boolean isNeedConfirmation, boolean isFutureDeliveryMail, DataQueue obDataQueue) {
        isConnected = false;
        this.obStrServerAddress = obStrServerAddress;
        this.obMailEnvelope = obMailEnvelope;
        this.isNeedConfirmation = isNeedConfirmation;
        this.isFutureDeliveryMail = isFutureDeliveryMail;
        this.obDataQueue = obDataQueue;
        this.obStrServerName = obStrServerName;
    }

    public void run() {
        try {
            //Connection to Server
            obSocket = new Socket(obStrServerAddress, obIntServerPort);
            obOutput = new PrintWriter(obSocket.getOutputStream(), true);
            obInput = new BufferedReader(new InputStreamReader(obSocket.getInputStream()));
            System.out.println("===Connected to SMTP server===");
            if (receiveCommand(220)) {
                sendEmail();
            } else {
                //F: 421
                processResponse(false, "Connection failed to Mail Server");
            }
            closeConnection();
        } catch (UnknownHostException unknownHostException) {
            processResponse(false, "Oops, Your email cannot be sent, Mail Server is Unknown");
            System.out.println("===Mail Server is unknown====");
        } catch (IOException ioexception) {
            processResponse(false, "Oops, Your email cannot be sent, connection failed to Mail Server");
            System.out.println("===Connection failed to Mail Server 1====");
        } catch (Exception exception) {
            processResponse(false, "Oops, Your email cannot be sent, connection failed to Mail Server");
            System.out.println("===Connection failed to Mail Server 2====");
        }
    }

    private void processResponse(boolean isSuccess, String obStrMessage) {
        /* If the email is sent using future delivery feature, then the sender must be notified the status of the email delivery */
        if (isNeedConfirmation) {
            DataHandler smtpHandler = new DataHandler(obMailEnvelope.getSender(), obMailEnvelope.getRecipient(),
                    isSuccess ? obDataQueue.STATUS_DELIVERED : obDataQueue.STATUS_FAILED);
        }
        if (!isFutureDeliveryMail) {
            /* If this email is not confirmation email then update the Queue data to be used by HTTP server as delivery page */
            Date dDate = new Date();
            DateFormat dFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            obDataQueue.updateMailQueueData(obMailEnvelope.getMailData().getDate(),
                    isSuccess ? dFormat.format(dDate) : null,
                    isSuccess ? 1 : 2,
                    obStrMessage);
        }
        sendQuit();
    }

    /**
     * send an smtp command to the server
     */
    private void sendCommand(String command) {
        try {
            System.out.println("Client Request : " + command);
            obOutput.println(command);
        } catch (Exception e) {
        }

    }
    /**
     * receive smtp reply code from the server
     */
    private final int SUCCESS = 250;
    private final int SUCCESS_DATA = 354;

    private boolean receiveCommand(int rc) {
        boolean isSuccess = false;
        try {
            String reply = obInput.readLine();
            System.out.println("Server Response : " + reply);
            setResponseCode(parseReply(reply));
            if (getResponseCode() == rc) {
                isSuccess = true;
            }
            return isSuccess;
        } catch (IOException ie) {
            return isSuccess;
        }
    }
    private int obIntResponseCode;

    private int getResponseCode() {
        return obIntResponseCode;
    }

    private void setResponseCode(int obIntCurrentError) {
        this.obIntResponseCode = obIntCurrentError;
    }
    private final int HELLO_STAGE = 0;
    private final int MAIL_STAGE = 1;
    private final int RCPT_STAGE = 2;
    private final int DATA_STAGE = 3;
    private final int ENVELOPE_STAGE = 4;
    private final int QUIT_STAGE = 5;

    private String getDeliveryDetail(int obIntDeliveryCode, int obIntStage) {
        String obStrDeliveryDetail = "";
        switch (obIntDeliveryCode) {
            case 250:
                if (obIntStage == ENVELOPE_STAGE) {
                    obStrDeliveryDetail = "Yipii, your mail is delivered";
                } else {
                    obStrDeliveryDetail = "Yeps, Your email is sent now.";
                }
                break;
            case 501://Syntax error in parameters or arguments
            case 503://
                //Bad sequence of commands
                obStrDeliveryDetail = "Oops, Your email cannot be sent, Server ununable to provide mail delivery now, please try again later.";
                break;
            case 421://Service not available, closing transmission channel
            case 451://Requested action aborted: error in processing
            case 452://Requested action not taken: insufficient system storage
            case 500://Syntax error, command unrecognized (This may include errors such as command line too long)
            case 551://User not local; please try <forward-path>
            case 554://Transaction failed  (Or, in the case of a connection-opening response, "No SMTP service here", "Relay access denied"
                obStrDeliveryDetail = "Oops, Your email cannot be sent, Server ununable to provide mail delivery to this recipient";
                break;
            case 450:
                //Requested mail action not taken: mailbox unavailable (e.g., mailbox busy)
                obStrDeliveryDetail = "Oops, Your email cannot be sent, Recipient's maibox is busy, please try again later.";
                break;
            case 550:
                //Requested action not taken: mailbox unavailable (e.g., mailbox not found, no access, or command rejected for policy reasons)
                obStrDeliveryDetail = "Oops, Your email cannot be sent, Recipient's maibox is unavailable and/or Server rejects the email, please try again later.";
                break;
            case 552:
                //equested mail action aborted: exceeded storage allocation
                if (obIntStage == DATA_STAGE) {
                    obStrDeliveryDetail = "Oops, Your email cannot be sent, too much mail data.";
                } else if (obIntStage == RCPT_STAGE) {
                    obStrDeliveryDetail = "Oops, Your email cannot be sent, Recipient's maibox is full, please try again later.";
                }
                break;
            case 553:
                //Requested action not taken: mailbox name not allowed  (e.g., mailbox syntax incorrect)
                obStrDeliveryDetail = "Oops, Your email cannot be sent, mailbox syntax is incorrect.";
                break;
            default:
                obStrDeliveryDetail = "Oops, Your email cannot be sent, there is unknown error, please try again later";
                break;
        }
        return obStrDeliveryDetail;
    }

    /**
     * parse the reply from the server return the reply code
     * @param reply
     * @return
     */
    private int parseReply(String reply) {
        StringTokenizer tokens = new StringTokenizer(reply);
        int code = 0;
        String tempStr = tokens.nextToken();
        try {
            code = Integer.parseInt(tempStr);
        } catch (Exception e1) {
            try {
                code = Integer.parseInt(tempStr.substring(0, 3));
            } catch (Exception e2) {
                code = -1;
            }
        }
        return code;
    }

    private void sendEmail() {
        isConnected = true;
        sendHeloEhlo(obStrServerName);
        if (getResponseCode() == SUCCESS) {
            sendEnvelope(obMailEnvelope);
        } else {
            sendQuit();
        }
    }

    /* open the connection */
    private void sendHeloEhlo(String obStrDomain) {
        try {
            sendCommand("HELO " + obStrDomain);
            if (receiveCommand(SUCCESS)) {
                if (!isFutureDeliveryMail) {
                    //Make new queue for status page
                    obDataQueue.updateMailQueueData(obMailEnvelope.getMailData().getDate(), null, 0, getDeliveryDetail(getResponseCode(), HELLO_STAGE));
                }
            } else {
                /* E: 500, 501, 504, 421 */
                processResponse(false, getDeliveryDetail(getResponseCode(), HELLO_STAGE));

            }
        } catch (Exception e) {
            processResponse(false, getDeliveryDetail(0, HELLO_STAGE));
            System.out.println("===Sending HELO failed===");
        }
    }

    /**
     * send the message
     * @param envelope envelope contain all the message
     * @throws IOException
     */
    private void sendEnvelope(MailEnvelope obMailEnvelope) {
        /* Envelope */
        String command = "MAIL FROM: " + obMailEnvelope.getSender();
        sendCommand(command);
        //Check answer
        if (receiveCommand(SUCCESS)) {
            command = "RCPT TO: " + obMailEnvelope.getRecipient();
            sendCommand(command);
            if (receiveCommand(SUCCESS)) {
                command = "DATA";
                sendCommand(command);
                if (receiveCommand(SUCCESS_DATA)) {
                    command = "";
                    /* Body */
                    command += "From: " + obMailEnvelope.getMailData().getFrom() + CRLF;
                    command += "Reply-To: " + obMailEnvelope.getMailData().getReplyTo() + CRLF;
                    command += "To: " + obMailEnvelope.getMailData().getTo() + CRLF;
                    if (!isFutureDeliveryMail) {
                        command += "Subject: =?ISO-8859-15?Q?" + MIMEutility.MIMEEncodingSubject(obMailEnvelope.getMailData().getSubject()) + "?=" + CRLF;
                    } else {
                        command += "Subject: " + obMailEnvelope.getMailData().getSubject() + CRLF;
                    }
                    command += "Date: " + obMailEnvelope.getMailData().getDate() + CRLF;

                    /* MIME */
                    MailMIMEHeader obMailMIME = obMailEnvelope.getMailMIME();
                    //command = "";
                    if (obMailMIME != null) {
                        command += "MIME-Version: " + obMailMIME.getMIMEVersion() + CRLF;
                        command += "Content-Type: " + obMailMIME.getContentType() + CRLF;
                        command += "Content-Transfer-Encoding: " + obMailMIME.getEncoding() + CRLF;
                    }
                    command += obMailEnvelope.getMailData().getBody();
                    /* Footer */
                    command += CRLF + ".";
                    sendCommand(command);
                    if (receiveCommand(SUCCESS)) {
                        processResponse(true, getDeliveryDetail(getResponseCode(), ENVELOPE_STAGE));
                    } else {
                        // 552 Too much mail data.
                        processResponse(false, getDeliveryDetail(getResponseCode(), ENVELOPE_STAGE));
                    }
                } else {
                    /*
                    F: 451, 554
                    E: 500, 501, 503, 421
                     */
                    processResponse(false, getDeliveryDetail(getResponseCode(), DATA_STAGE));
                }
            } else {
                /*
                F: 550, 551, 552, 553, 450, 451, 452
                E: 500, 501, 503, 421
                 */
                processResponse(false, getDeliveryDetail(getResponseCode(), RCPT_STAGE));
            }
        } else {
            /*
            F: 552, 451, 452
            E: 500, 501, 421
             */
            processResponse(false, getDeliveryDetail(getResponseCode(), MAIL_STAGE));
        }
    }

    /* close the connection */
    private void sendQuit() {
        try {
            sendCommand("QUIT" + CRLF);
            receiveCommand(221);
        } catch (Exception e) {
            System.out.println("===Closing Email Session failed===");
        }

    }

    private void closeConnection() throws IOException {
        try {
            obOutput.close();
            obInput.close();
            obSocket.close();
            isConnected = false;
        } catch (Exception e) {
            System.out.println("===Closing Connection failed===");
        }
    }
}
